"""
A Python implementation of the One-order Markov model to predict 
users next access domain in a session.

"""

import fileinput

def one_markov(train_dataset_path,test_dataset_path,no_of_recommendation,mode_of_learning):
    markov_dic={} # this dictionary will have all the transition probabilities
    rec_count=0
    accuracy=0
    session_count=0
    no_of_request=0
    no_request=0
    print "Training"
    for session in fileinput.input([train_dataset_path]):
            try:
                session_count=session_count+1
                train(session,session_count,markov_dic)
            except:
                continue
    print "Training Finished"
    
    print "Testing"
    for session in fileinput.input([test_dataset_path]):
        try:
            accuracy,rec_count,no_of_request,no_request=test(session,session_count,accuracy,rec_count,markov_dic,no_of_request,no_request,no_of_recommendation)
            if (mode_of_learning=='c'):
                train(session,session_count,markov_dic)
        except:
                continue
 
def train(session, session_count,markov_dic):
    '''
     # The following method u is used to train the session and the out put is 
     the transition matrix of one-order markov model as a dictionary 
    '''
    total_frequency=0
    session= session.strip().rstrip(',')
    session_elements=session.split(',') # to get the elements/domain number in a sessions
    length=len(session_elements)-1
    for i in range(0,length):
        j=i+1
        if markov_dic.has_key(session_elements[i]):# check whether elements[i] is in the  transition matrix
            value=markov_dic.get(session_elements[i])
            total_frequency=value[0]
            inner_dic=value[1]
            total_frequency=int(total_frequency)+1
            if inner_dic.has_key(session_elements[j]):
                inner_dic[session_elements[j]]=int(inner_dic.get(session_elements[j]))+1
            else:
                inner_dic[session_elements[j]]=1
                markov_dic[session_elements[i]]=(total_frequency,inner_dic)
        else:
            total_frequency=1
            inner_dic={}
            inner_dic[session_elements[j]]=1
            markov_dic[session_elements[i]]=(total_frequency,inner_dic)


def test(session,n,accuracy,rec_count,markov_dic,no_of_request,no_request,no_of_recommendation):
    '''
    This method will be used for testing  the incoming session 
    '''

    recommendation_list=[]
    session= session.strip().rstrip(',')
    session_elements=session.split(',')
    length=len(session_elements)-1
    for i in range(0,length):
        no_of_request+=1
        recommendation_list=recommend(session_elements[i],markov_dic,no_of_recommendation)
        length_of_rec=len(recommendation_list)
        if length_of_rec==0:
            no_request+=1
        if not length_of_rec==0:
            rec_count+=1
            j=i+1
            evaluation_results=evaluate(recommendation_list,session_elements[j],rec_count,accuracy)   
            accuracy=evaluation_results[0] 

    coverage=float(no_of_request-no_request)/float(no_of_request)*100
    print"----------------------"
    print "Average:", accuracy
    print "Coverage:",coverage
    return accuracy,rec_count,no_of_request,no_request


def recommend(domain,markov_dic,no_of_recommendation):
    '''
    This method will recommend the domains based on the transition probability matrix(markov_dic)
    and the number of recommendations can be limited by the use of parameter no_of_recommendation
    '''
    recommendation_list =[]
    inner_dic=markov_dic.get(domain)
    if (inner_dic==None):
        return recommendation_list
    value= inner_dic[1]
    recommendation_list=sorted(value.iteritems(), key=lambda (k,v): (v,k)) # sort the key of dictionary based on value
    recommendation_list.reverse()
    length_of_rec=len(recommendation_list)
    result_list=[]
    for u in range(0,length_of_rec):
        result_list.append((recommendation_list[u])[0])
    return result_list[0:no_of_recommendation]


def evaluate(recommendationlist,actual,count,accuracy):
    '''
    This method is used to evaluate the recommendations generated by the recommend function
    returns accuracy and recommendation count
    '''
    correct=0
    if actual in recommendationlist:
        accuracy = calculate_accuracy(1,accuracy,count)
        return (accuracy,count,correct)
    else:
        accuracy=calculate_accuracy(0,accuracy,count)
        return (accuracy,count,correct)
    
def calculate_accuracy(correct,accuracy,count):
    '''
    This method is used to calculate accuracy 
    '''
    return ((int(count)-1)*float(accuracy)+int(correct))/int(count)



if __name__ == "__main__":one_markov('D:\\train_d.csv','D:\\test_d.csv',20,'c')

